package com.stylefeng.guns.modular.business.service.impl;

import cn.wanghaomiao.seimi.spring.common.CrawlerCache;
import cn.wanghaomiao.seimi.struct.CrawlerModel;
import cn.wanghaomiao.seimi.struct.Response;
import com.google.common.collect.Lists;
import com.stylefeng.guns.common.enums.DroolsPathEnum;
import com.stylefeng.guns.common.enums.DxResultEnum;
import com.stylefeng.guns.common.enums.SingleResultEnum;
import com.stylefeng.guns.common.enums.StatusEnum;
import com.stylefeng.guns.common.persistence.dao.DxExpectMapper;
import com.stylefeng.guns.common.persistence.dao.GameMapper;
import com.stylefeng.guns.common.persistence.dao.HistoryMapper;
import com.stylefeng.guns.common.persistence.dao.LotteryMapper;
import com.stylefeng.guns.common.persistence.dao.SingleExpectMapper;
import com.stylefeng.guns.common.persistence.model.DxExpect;
import com.stylefeng.guns.common.persistence.model.Game;
import com.stylefeng.guns.common.persistence.model.History;
import com.stylefeng.guns.common.persistence.model.Lottery;
import com.stylefeng.guns.common.persistence.model.SingleExpect;
import com.stylefeng.guns.core.util.CommonUtil;
import com.stylefeng.guns.core.util.DroolsUtil;
import com.stylefeng.guns.crawlers.zgzc.BaseZgcpCrawler;
import com.stylefeng.guns.modular.business.service.IGameService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author wuchong
 */
@Slf4j
@Service
public class GameServiceImpl extends BaseZgcpCrawler implements IGameService {

    @Autowired
    private GameMapper gameMapper;

    @Autowired
    private HistoryMapper historyMapper;

    @Autowired
    private LotteryMapper lotteryMapper;

    @Autowired
    private DxExpectMapper dxExpectMapper;

    @Autowired
    private SingleExpectMapper singleExpectMapper;

//    @Resource(name = "jsonTemplate")
//    private RedisTemplate redisTemplate;

//    @Resource
//    private StringRedisTemplate stringRedisTemplate;

    @Resource(name = "droolsSessionMap")
    private Map<String, KieSession> kieSessionMap;

    /**
     * 聚合比赛数据
     *
     * @param game
     * @param histories
     * @param lotteries
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void generateGame(Game game, List<History> histories, List<Lottery> lotteries) {
        try {
            Game game1 = aggregate(game, histories, lotteries);


            gameMapper.insertSelective(game1);
            dxExpectMapper.insert(game1.getDxExpect());
            singleExpectMapper.insert(game1.getSingleExpect());

            historyMapper.insertList(histories);
            lotteryMapper.insertList(lotteries);

            log.info("保存比赛{}的各项数据成功", game1.getMid());

//            // 进行缓存
//            stringRedisTemplate.opsForList().leftPush(CACHE_KEY, game1.getMid());

            log.info("缓存比赛{}的各项数据成功", game1.getMid());

        } catch (IllegalArgumentException e) {
            log.warn("比赛mid={}获取不到正确的数据，错误信息为={}", game.getMid(), e.getMessage());
        }
    }

    @Override
    public List<Game> list(Game game) {

        List<Game> list = gameMapper.selectByCondition(game);
        return list.parallelStream()
                .sorted(Comparator.comparing(Game::getExpire).reversed().thenComparing(Comparator.comparing(Game::getTime)))
                .collect(Collectors.toList());
    }

    @Override
    public Game viewStat(String mid) {

        Game game = gameMapper.findByMid(mid);
        game.setDxResult(DxResultEnum.getNameByCode(game.getDxResult()));
        game.setSfResult(SingleResultEnum.getNameByCode(game.getSfResult()));

        return game;
    }

    /**
     * 更新比赛结果
     *
     * @param games 比赛记录
     */
    @Override
    public void updateGame(List<Map<String, String>> games) {
        List<Game> gameList = Lists.newArrayList();
        List<String> mids = gameMapper.findMid();
        List<DxExpect> dxExpects = Lists.newArrayList();
        List<SingleExpect> singleExpects = Lists.newArrayList();
        games.forEach(x -> {
            String actualScore = x.get("score");
            String mid = x.get("mid");
            if (mids.contains(mid) && StringUtils.contains(actualScore, ":")) {
                Game game = Game.builder().mid(mid).expire(StatusEnum.DONE.getCode())
                        .actualScore(actualScore).build();
                DxExpect dxExpect = new DxExpect(mid);
                SingleExpect singleExpect = new SingleExpect(mid);
                String[] scores = actualScore.split("[:-]");
                int homeGoal = Integer.parseInt(scores[0]);
                int awayGoal = Integer.parseInt(scores[1]);
                int goals = homeGoal + awayGoal;
                dxExpect.setResult(goals >= 3 ? DxResultEnum.BIG.getCode() : DxResultEnum.SMALL.getCode());

                String sfResult = getActualSfResult(mid, homeGoal, awayGoal);
                singleExpect.setResult(sfResult);

                dxExpects.add(dxExpect);
                singleExpects.add(singleExpect);
                gameList.add(game);

            }
        });
        dxExpectMapper.batchUpdate(dxExpects);
        singleExpectMapper.batchUpdate(singleExpects);
        gameMapper.batchUpdate(gameList);
        log.info("需要更新的比赛记录{}成功", games);
    }

    /**
     * 重新计算
     *
     * @param mids
     */
    @Override
    public void recalculate(String... mids) {

        long start = System.currentTimeMillis();
        for (String mid : mids) {
            // game info
            Game game1 = gameMapper.findByMid(mid);
            List<History> histories = historyMapper.findbyMidAndcompType(mid, null);
            List<Lottery> lotteries = lotteryMapper.findByMid(mid);
            // 计算比赛记录
            Game game = aggregate(game1, histories, lotteries);
            // 更新比赛记录
            gameMapper.updateByPrimaryKeySelective(game);

            // 更新大小预测
            Example example = new Example(DxExpect.class);
            example.createCriteria().andEqualTo("mid", game.getMid());
            dxExpectMapper.updateByExampleSelective(game.getDxExpect(), example);
            // 更新上下盘预测
            Example example1 = new Example(SingleExpect.class);
            example1.createCriteria().andEqualTo("mid", game.getMid());
            singleExpectMapper.updateByExampleSelective(game.getSingleExpect(), example1);

        }
        log.info("重新计算比赛{}耗时{}ms", mids, System.currentTimeMillis() - start);
    }

    /**
     * 更新某一条记录
     *
     * @param mids
     */
    @Override
    public void updateRecord(String... mids) {

        for (String mid : mids) {
            try {
                Game game = gameMapper.findByMid(mid);
                List<History> histories = getHistoryList(game);
                List<Lottery> lotteries = getLotteryList(game);
                generateGame(game, histories, lotteries);

                gameMapper.updateByPrimaryKeySelective(game);
                // 更新大小预测
                Example example = new Example(DxExpect.class);
                example.createCriteria().andEqualTo("mid", game.getMid());
                dxExpectMapper.updateByExampleSelective(game.getDxExpect(), example);
                // 更新上下盘预测
                Example example1 = new Example(SingleExpect.class);
                example1.createCriteria().andEqualTo("mid", game.getMid());
                singleExpectMapper.updateByExampleSelective(game.getSingleExpect(), example1);

                List<History> existHistories = historyMapper.findbyMidAndcompType(mid, null);
                List<Lottery> existLotteries = lotteryMapper.findByMid(mid);
                // 求差集
                List<History> historyInserts = ListUtils.subtract(histories, existHistories);
                List<Lottery> lotteryInserts = ListUtils.subtract(lotteries, existLotteries);
                historyMapper.insertList(historyInserts);
                lotteryMapper.insertList(lotteryInserts);

            } catch (Exception e) {
                log.error("更新比赛{}数据失败", mid, e);
            }

        }
    }


    private String getActualSfResult(String mid, Integer homeGoal, Integer awayGoal) {
        String sfResult = "";
        SingleExpect singleExpect1 = singleExpectMapper.findByMid(mid);

        String isLow = singleExpect1.getIsLow();
        double handicap = singleExpect1.getHandicap();
        if ("1".equals(isLow)) {
            // 主队低水
            if (handicap + homeGoal > awayGoal) {
                sfResult = SingleResultEnum.UP.getCode();
            } else {
                sfResult = SingleResultEnum.DOWN.getCode();
            }
        } else {
            // 客队低水
            if (handicap + homeGoal >= awayGoal) {
                sfResult = SingleResultEnum.DOWN.getCode();
            } else {
                sfResult = SingleResultEnum.UP.getCode();
            }
        }

        return sfResult;
    }

    private Game aggregate(Game game, List<History> histories, List<Lottery> lotteryList) {

        Assert.notNull(game, "比赛基础数据不能为空");
        Assert.notEmpty(histories, "比赛历史对阵数据不能为空");
        Assert.notEmpty(lotteryList, "比赛大小对比数据不能为空");

        // 计算结果
        Example example = new Example(History.class);
        example.createCriteria().andEqualTo("mid", game.getMid());

        // 设置比赛历史对阵数据
        setHistory(game, histories);
        // 设置大小盘口数据
        setDxHandicap(game, lotteryList);
        // 设置上下盘口数据
        setOverUnderHandicap(game, lotteryList);

        return game;
    }

    /**
     * 设置上下盘口数据
     *
     * @param game
     * @param lotteries
     */
    private void setOverUnderHandicap(Game game, List<Lottery> lotteries) {

        SingleExpect singleExpect = game.getSingleExpect();
        // 澳门或者bet365
        Lottery aomen = lotteries.parallelStream().filter(x -> StringUtils.equals("澳门", x.getCompany())).findAny().orElse(
                lotteries.parallelStream().filter(x -> StringUtils.equals("Bet365", x.getCompany())).findAny().orElse(null));
        // 立博
        Lottery libo = lotteries.parallelStream().filter(x -> StringUtils.equals("立博", x.getCompany())).findAny().orElse(null);
        // 威廉
        Lottery willian = lotteries.parallelStream().filter(x -> StringUtils.equals("威廉希尔", x.getCompany())).findAny().orElse(null);
        if (aomen != null && libo != null && willian != null) {
            // 澳门（没开盘bet365）开出的亚盘
            double asianHandicap = aomen.getAsianHandicap();
            // 计算上盘（独赢）概率
            double zWinProbability = CommonUtil.calWinProbability(50, singleExpect.getHomeScoreExpect(), singleExpect.getAwayScoreExpect());
            double kWinProbability = CommonUtil.calWinProbability(50, singleExpect.getAwayScoreExpect(), singleExpect.getHomeScoreExpect());
            double winProbability = 0d;
            String isLow = willian.getWinOdds() < willian.getLossOdds() ? "1" : "0";
            // 主队低水为上盘
            if ("1".equals(isLow)) {
                winProbability = zWinProbability;
            } else {
                winProbability = kWinProbability;
            }
            singleExpect.setAsianHandicap(asianHandicap);
            singleExpect.setHomeWinProbability(zWinProbability);
            singleExpect.setAwayWinProbability(kWinProbability);
            singleExpect.setWinProbability(winProbability);
            singleExpect.setActuarialOdds(game.getPfl() / winProbability);
            singleExpect.setWlOdds(Math.min(willian.getWinOdds(), willian.getLossOdds()));
            singleExpect.setLbOdds(Math.min(libo.getWinOdds(), libo.getLossOdds()));
            singleExpect.setIsLow(isLow);

            if (isValid(singleExpect)) {
                // 调用drools规则引擎
                DroolsUtil.executeRule(kieSessionMap.get(DroolsPathEnum.DROOLS_SF.getName()), singleExpect);
                // 设置比赛大小球预测结果
                game.setSfResult(getResultFromForecast(singleExpect.getForecast()));
            }
        }

    }

    /**
     * 判断上下盘是否一致
     *
     * @param singleExpect
     * @return
     */
    private boolean isValid(SingleExpect singleExpect) {

        // 判定上下盘一致
        if (singleExpect.getAsianHandicap() < 0 && singleExpect.getHandicap() > 0) {
            return false;
        }
        if (singleExpect.getAsianHandicap() > 0 && singleExpect.getHandicap() < 0) {
            return false;
        }
        if (singleExpect.getAsianHandicap() == 0 && singleExpect.getHandicap() < 0) {
            return singleExpect.getIsLow().equals("0");
        }
        return singleExpect.getAsianHandicap() != 0 || singleExpect.getHandicap() >= 0 || singleExpect.getIsLow().equals("1");
    }

    /**
     * 设置大小盘口数据
     *
     * @param game
     * @param lotteries
     */
    private void setDxHandicap(Game game, List<Lottery> lotteries) {

        DxExpect dxExpect = game.getDxExpect();
        // 澳门或者bet365
        Lottery aomen = lotteries.parallelStream().filter(x -> StringUtils.equals("澳门", x.getCompany())).findAny().orElse(null);
        Lottery bet365 = lotteries.parallelStream().filter(x -> StringUtils.equals("Bet365", x.getCompany())).findAny().orElse(null);
        Lottery lottery = Optional.ofNullable(aomen).orElse(bet365);
        // 立博
        Lottery libo = lotteries.parallelStream().filter(x -> StringUtils.equals("立博", x.getCompany())).findAny().orElse(null);

        if (lottery == null || libo == null) {
            throw new IllegalArgumentException("获取不到澳门或立博的盘口信息");
        }
        // 立博初始赔付率
        double pfl = libo.getPfl();
        game.setPfl(pfl);

        // 大小球初盘
        double dxHandicap = lottery.getDxHandicap();
        // 立博小2.5球初盘赔率
        double smallOdds = libo.getSmallOdds();
        dxExpect.setHandicap(dxHandicap);
        dxExpect.setSmallOdds(smallOdds);
        // 澳门大小球盘口
        dxExpect.setMacaoHandicap(Optional.ofNullable(aomen).map(Lottery::getDxHandicap).orElse(null));

        // 利用泊松分布，及期望进球数，计算本场比赛0-2球的概率
        double expected = dxExpect.getExpected();
        double poisson = CommonUtil.poisson(2, expected, true);
        dxExpect.setPoisson(poisson);
        // 利用2得出的概率，使用0.94/概率，得出本场比赛小球2球的精算赔率；
        double actual = game.getPfl() / poisson - 1;
        dxExpect.setActuarialOdds(actual);

        // 调用drools规则引擎
        DroolsUtil.executeRule(kieSessionMap.get(DroolsPathEnum.DROOLS_DX.getName()), dxExpect);

        // 设置比赛大小球预测结果
        game.setDxResult(getResultFromForecast(dxExpect.getForecast()));
    }

    /**
     * 设置比赛数据
     *
     * @param gameVo
     * @param histories
     */
    private void setHistory(Game gameVo, List<History> histories) {

        SingleExpect singleExpect = new SingleExpect(gameVo.getMid());
        DxExpect dxExpect = new DxExpect(gameVo.getMid());

        // 主队近六场联赛得失球总数
        List<History> homecomp = histories.stream().filter(x -> "homecomp".equals(x.getCompType())).sorted(History::compareTo).collect(Collectors.toList());
        List<History> awaycomp = histories.stream().filter(x -> "awaycomp".equals(x.getCompType())).sorted(History::compareTo).collect(Collectors.toList());
        List<History> vscomp = histories.stream().filter(x -> "vscomp".equals(x.getCompType())).sorted(History::compareTo).collect(Collectors.toList());

        if (homecomp.size() < 6 || awaycomp.size() < 6 || vscomp.size() < 6) {
            throw new IllegalArgumentException("获取不到完整的历史对阵数据");
        }

        String homeGoals = homecomp.stream().map(x -> String.valueOf(x.getGoal())).collect(Collectors.joining(","));
        String awayGoals = awaycomp.stream().map(x -> String.valueOf(x.getGoal())).collect(Collectors.joining(","));
        String vsGoals = vscomp.stream().map(x -> String.valueOf(x.getGoal())).collect(Collectors.joining(","));

        gameVo.setHomeGoals(homeGoals);
        gameVo.setAwayGoals(awayGoals);
        gameVo.setVsGoals(vsGoals);

        // 主队近6场进球数
        List<Integer> homeGain = homecomp.parallelStream().map(this::getHomeGain).collect(Collectors.toList());
        // 主队近6场失球数
        List<Integer> homeLoss = homecomp.parallelStream().map(this::getHomeLoss).collect(Collectors.toList());
        // 主客队近6场对阵主队进球数
        List<Integer> vsHomeGain = vscomp.parallelStream().map(this::getHomeGain).collect(Collectors.toList());

        // 客队近6场进球数
        List<Integer> awayGain = awaycomp.parallelStream().map(this::getHomeGain).collect(Collectors.toList());
        // 客队近6场失球数
        List<Integer> awayLoss = awaycomp.parallelStream().map(this::getHomeLoss).collect(Collectors.toList());
        // 主客队近6场对阵客队进球数
        List<Integer> vsAwayGain = vscomp.parallelStream().map(this::getHomeLoss).collect(Collectors.toList());

        gameVo.setHomeGain(homeGain.stream().map(String::valueOf).collect(Collectors.joining(",")));
        gameVo.setHomeLoss(homeLoss.stream().map(String::valueOf).collect(Collectors.joining(",")));
        gameVo.setVsHomeGain(vsHomeGain.stream().map(String::valueOf).collect(Collectors.joining(",")));
        gameVo.setAwayGain(awayGain.stream().map(String::valueOf).collect(Collectors.joining(",")));
        gameVo.setAwayLoss(awayLoss.stream().map(String::valueOf).collect(Collectors.joining(",")));
        gameVo.setVsAwayGain(vsAwayGain.stream().map(String::valueOf).collect(Collectors.joining(",")));

        // 主进球预期
        double homeGainExpect = gainExpect(homeGain, vsHomeGain);
        // 主失球预期
        double homeLossExpect = lossExpect(homeLoss);
        // 客进球预期
        double awayGainExpect = gainExpect(awayGain, vsAwayGain);
        // 客失球预期
        double awayLossExpect = lossExpect(awayLoss);

        // 大小球预期
        double dxExpected = (homeGainExpect + homeLossExpect + awayGainExpect + awayLossExpect) / 3;
        dxExpect.setExpected(dxExpected);
        // 本场主队球数预期
        double homeGoalsExpect = (homeGainExpect + awayLossExpect) / 3;
        // 本场客队球数预期
        double awayGoalsExpect = (awayGainExpect + homeLossExpect) / 3;
        // 预期盘口
        double expectHandicap = homeGoalsExpect - awayGoalsExpect;

        singleExpect.setHomeScoreExpect(homeGoalsExpect);
        singleExpect.setAwayScoreExpect(awayGoalsExpect);
        singleExpect.setHandicap(expectHandicap);

        gameVo.setSingleExpect(singleExpect);
        gameVo.setDxExpect(dxExpect);
    }

    /**
     * 进球预期
     *
     * @param list1
     * @param list2
     * @return
     */
    private double gainExpect(List<Integer> list1, List<Integer> list2) {

        Integer sum1 = list1.stream().mapToInt(x -> x).sum();
        Integer sum2 = list1.stream().limit(3).mapToInt(x -> x).sum();
        Integer sum3 = list2.stream().limit(3).mapToInt(x -> x).sum();
        Integer sum4 = list2.stream().mapToInt(x -> x).sum();

        return sum1 / 12d + sum2 / 6d + sum3 / 6d + sum4 / 12d;
    }


    /**
     * 失球预期
     *
     * @param list1
     * @return
     */
    private double lossExpect(List<Integer> list1) {

        Integer sum1 = list1.stream().mapToInt(x -> x).sum();
        Integer sum2 = list1.stream().limit(3).mapToInt(x -> x).sum();

        return sum1 / 12d + sum2 / 6d;
    }

    /**
     * 主队进球数
     *
     * @param x
     * @return
     */
    private Integer getHomeGain(History x) {
        String score = x.getScore();
        String[] strings = StringUtils.split(score, ":");
        String flag = x.getFlag();
        if ("0".equals(flag)) {
            return Integer.parseInt(strings[0]);
        } else {
            return Integer.parseInt(strings[1]);
        }
    }

    /**
     * 主队失球数
     *
     * @param x
     * @return
     */
    private Integer getHomeLoss(History x) {
        String score = x.getScore();
        String[] strings = StringUtils.split(score, ":");
        String flag = x.getFlag();
        if ("1".equals(flag)) {
            return Integer.parseInt(strings[0]);
        } else {
            return Integer.parseInt(strings[1]);
        }
    }

    /**
     * 更新数据
     *
     * @param
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void update() {

        CrawlerModel crawlerModel = CrawlerCache.getCrawlerModel("updateBdCraw");
        if (crawlerModel == null) {
            log.warn("Crawler name = {} is not existent.", "updateBdCraw");
        } else {
            crawlerModel.startRequest();
        }
    }

    /**
     * 开启爬取数据
     */
    @Override
    public void start(Integer type) {

        CrawlerModel crawlerModel;
        if (type == 1) {
            crawlerModel = CrawlerCache.getCrawlerModel("otherCrawler");
        } else {
            crawlerModel = CrawlerCache.getCrawlerModel("bdCrawler");
        }
        if (crawlerModel == null) {
            log.warn("Crawler name = {} is not existent.");
        } else {
            crawlerModel.startRequest();
        }
    }

    private String getResultFromForecast(String forecast) {

        if (DxResultEnum.NONE.getCode().equals(forecast) || DxResultEnum.CHECKED.getCode().equals(forecast)) {
            return "0";
        } else {
            return forecast;
        }
    }

    /**
     * @return 设置起始url
     */
    @Override
    public String[] startUrls() {
        return new String[0];
    }

    /**
     * 针对startUrl生成首批的response回调这个初始接口
     *
     * @param response --
     */
    @Override
    public void start(Response response) {
        System.out.println("success");
    }
}
